Support for Advanced Navigation ANPP format#861
Conversation
020b239 to
b4f5fd4
Compare
|
Hi @rtklibexplorer. This is my first PR to RTKLIB. I'm curious if there is any procedure you have for reviewing/merging, and what standard of testing you expect for incoming PRs. |
|
|
||
| /* map ANPP (sys, freq_code) to rtklib observation code ---------------------*/ | ||
| static uint8_t anpp2code(uint8_t anpp_sys, uint8_t freq_code) | ||
| { |
There was a problem hiding this comment.
Perhaps add all the documented signals. For BDS it appears to be just B1 B2 B3 and might the following not be the most relevant reference. Would they limit Trimble receiver processing to just these signals, and BDS2 has been largely retired now and there is no reference to the B1C / B2A signals. For QZSS there is no definition for L1C/B for J04 and J08. This reference looks a little stale.
https://docs.advancednavigation.com/boreas-d/ANPP/RawSatelliteDataPacket.htm#Raw_Satellite_Data_Packet
There was a problem hiding this comment.
Will add. From the bottom of that page: © 2026 Advanced Navigation. All Rights Reserved. Version:2.5.1 Date: 2026-03-24. I know for a fact that it contains the latest version of the messages for 60 and 61 because the firmware including them was recently updated. That being said, I don't discount the possibility that a more recent source is available, so will look around.
There was a problem hiding this comment.
Also, I have an off-by-one here
There was a problem hiding this comment.
I've cut the Gordian knot here and reached out directly to the source (Advanced Navigation). Hoping this will get resolved soon, since I frankly don't know enough to get these mappings absolutely right.
| return 0; | ||
| } | ||
| anpp->time.time = (time_t)U4(p + 4); | ||
| anpp->time.sec = U4(p + 8) * 1e-6; |
There was a problem hiding this comment.
Could further abstract these raw packet accesses adding a bounds check if you wanted an extra layer of defence in depth. See for example my branch and the septentrio decoder where it defines e.g. the following, and I have the same for some other decoders in other private branches used for testing.
static uint32_t U4(const raw_t *raw, size_t index) {
RTKBOUNDSCHECK(raw->buff, sizeof(raw->buff), index + 3);
RTKBOUNDSCHECK(raw->buff, raw->len, index + 3);
uint32_t u;
memcpy(&u, raw->buff + index, 4);
return u;
}
then this becomes the following and you have a little extra peace of mind, and avoiding pointer arithmetic makes the code easier to translate to a higher level language with bounds checked array access.
anpp->time.time = (time_t)U4(raw, 5 + 4);
anpp->time.sec = U4(raw, 5 + 8) * 1e-6;
There was a problem hiding this comment.
For most of these, the bounds check is overkill since they are fixed known-good offsets. Out of curiosity though, which branch are you referring to. I did not see it among the merged or unmerged ones I spot-checked.
There was a problem hiding this comment.
Fwiw personally would also like to see it move away from pointer arithmetic. These functions also check against the raw->len and not just the buffer size. https://github.com/ourairquality/RTKLIB/blob/oaq/src/rcv/septentrio.c#L447
Fixed/added signal codes Added missing OmniStar and NavIC satellites/signals Simplified variable declarations Added to CMakeLists
|
@ourairquality I've addressed as much of your feedback as I could for the moment. Waiting for feedback from Advanced Navigation. In the meantime, I've continued testing this with the |
| case ANPP_SYS_GPS: | ||
| switch (freq_code) { | ||
| case 1: return CODE_L1C; /* L1 C/A */ | ||
| case 2: return CODE_L1C; /* L1 C */ |
There was a problem hiding this comment.
L1 C is likely rinex L1L (or L1S or L1X)
| case 1: return CODE_L1C; /* L1 C/A */ | ||
| case 2: return CODE_L1C; /* L1 C */ | ||
| case 3: return CODE_L1P; /* L1 P */ | ||
| case 4: return CODE_L1M; /* L1 M */ |
There was a problem hiding this comment.
While this might really be L1M, for non-mil it is likely rinex L1W.
| case 3: return CODE_L1P; /* L1 P */ | ||
| case 4: return CODE_L1M; /* L1 M */ | ||
| case 5: return CODE_L2C; /* L2 C */ | ||
| case 6: return CODE_L2P; /* L2 P */ |
There was a problem hiding this comment.
Guess rinex L2W, except dup of L2M below?
| case 5: return CODE_L2C; /* L2 C */ | ||
| case 6: return CODE_L2P; /* L2 P */ | ||
| case 7: return CODE_L2M; /* L2 M */ | ||
| case 8: return CODE_L5X; /* L5 */ |
There was a problem hiding this comment.
If their receiver is Trimble based then 5X otherwise guess L5Q.
| case 3: return CODE_L1P; /* G1 P */ | ||
| case 5: return CODE_L2C; /* G2 C/A */ | ||
| case 6: return CODE_L2P; /* G2 P */ | ||
| case 8: return CODE_L3X; /* G3 */ |
There was a problem hiding this comment.
Guess L3X for Trimble otherwise L3Q.
| case 4: return CODE_L1M; /* L1 M */ | ||
| case 5: return CODE_L2C; /* L2 C */ | ||
| case 6: return CODE_L2P; /* L2 P */ | ||
| case 7: return CODE_L2M; /* L2 M */ |
| case 1: return CODE_L1C; /* L1 C/A */ | ||
| case 2: return CODE_L1C; /* L2 C */ | ||
| case 3: return CODE_L1Z; /* L1 SAIF */ | ||
| case 5: return CODE_L2X; /* L2 C */ |
There was a problem hiding this comment.
Trimble L2X otherwise guess L2L (or L2S).
| case 2: return CODE_L1C; /* L2 C */ | ||
| case 3: return CODE_L1Z; /* L1 SAIF */ | ||
| case 5: return CODE_L2X; /* L2 C */ | ||
| case 6: return CODE_L6X; /* LEX */ |
| case 3: return CODE_L1Z; /* L1 SAIF */ | ||
| case 5: return CODE_L2X; /* L2 C */ | ||
| case 6: return CODE_L6X; /* LEX */ | ||
| case 8: return CODE_L5Z; /* L5 ?? */ |
| return 0; | ||
| } | ||
| anpp->time.time = (time_t)U4(p + 4); | ||
| anpp->time.sec = U4(p + 8) * 1e-6; |
There was a problem hiding this comment.
Fwiw personally would also like to see it move away from pointer arithmetic. These functions also check against the raw->len and not just the buffer size. https://github.com/ourairquality/RTKLIB/blob/oaq/src/rcv/septentrio.c#L447
Format defined here: https://docs.advancednavigation.com/boreas-d/ANPP/Advanced%20Navigation%20Packet.htm
This implementation only handles the raw satellite measurements, not the entire protocol.
Receiver selection is done in the same way as for SBF format: the
optfield is searched for-RCVR{n}, wherenmust be an integer in the range [0-255] since ANPP theoretically supports that many antennae per unit.Testing
Compared raw output from Boreas D-90 parsed with https://github.com/saronic-technologies/liban-rs/ (see saronic-technologies/liban-rs#18) to result of parsing with rust bindings to this library in https://github.com/kpwebb/rtklib-ffi/ (see joe-saronic/rtklib-ffi#1). Verified that obs fragments are handled correctly.
Inspected output of convbin from two receivers in a well-known dataset to ensure correct numbers of observations, constellations, etc. Main dataset can not be provided, but a small edited sample is available in the integration tests of the rust wrapper.